﻿using System.Collections.Generic;

using Vestras.StarCraft2.Luna.Parser;
using Vestras.StarCraft2.Luna.Parser.Ast;

namespace Vestras.StarCraft2.Luna.Validation {
    public sealed class FunctionTypeValidator : ITypeValidator {
        public System.Type[] Type {
            get {
                return new[] { typeof(Function) };
            }
        }

        public bool Validate(object value) {
            if (value != null && value is Function) {
                ErrorSink.UpdateChangeCollection();
                Function function = value as Function;
                if (!function.IsPrototype && function.Prototype != null) {
                    Function prototype = function.Prototype;
                    bool fault = false;
                    if (function.Name != prototype.Name) {
                        fault = true;
                    }

                    if (function.Type.GetQualifiedId() != prototype.Type.GetQualifiedId()) {
                        fault = true;
                    }

                    if (function.Parameters.Count != prototype.Parameters.Count) {
                        fault = true;
                    }

                    int index = 0;
                    foreach (var param in function.Parameters) {
                        var equivalentParam = prototype.Parameters[index];
                        if (param == null || equivalentParam == null) {
                            fault = true;
                        }

                        if (param.Name != equivalentParam.Name) {
                            fault = true;
                        }

                        if (param.Type.GetQualifiedId() != equivalentParam.Type.GetQualifiedId()) {
                            fault = true;
                        }

                        index++;
                    }

                    if (fault) {
                        ErrorSink.AddError(ErrorSink.Error.Create("Function signature of function '" + function.Name + "' does not match function prototype signature of function prototype '" + prototype.Name + "'", function));
                        return false;
                    }
                }

                if (function.Type.ArrayDimensionExpressions.Count > 0) {
                    ErrorSink.AddError(ErrorSink.Error.Create("Array types are not supported for return values.", function.Type));
                    return false;
                }

                foreach (Variable param in function.Parameters) {
                    if (param.Type.ArrayDimensionExpressions.Count > 0) {
                        ErrorSink.AddError(ErrorSink.Error.Create("Array types are not supported for parameter types.", param.Type));
                        return false;
                    }
                }

                IList<Entity> types = new List<Entity>(function.Type.GetReferencedTypes());
                if (types.Count == 0) {
                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot find type '" + function.Type.GetQualifiedId() + "'", function.Type));
                    return false;
                }

                string ambiguityEntities;
                if (types.IsAmbiguous(out ambiguityEntities)) {
                    ErrorSink.AddError(ErrorSink.Error.Create("Ambiguity between types " + ambiguityEntities, function.Type));
                    return false;
                }

                Entity typeEntity = types[0];
                if (typeEntity is Struct) {
                    Struct s = typeEntity as Struct;
                    if (s.Parent == null) {
                        if (s.FileName == function.FileName) {
                            if (s.OrderIndex > function.OrderIndex) {
                                ErrorSink.AddError(ErrorSink.Error.Create("Cannot access struct '" + s.Name + "' because it has not yet been declared", function.Type));
                                return false;
                            }
                        } else {
                            if (s.FileName != null && !s.FileName.IsIncludedByFile(function.FileName)) {
                                ErrorSink.AddError(ErrorSink.Error.Create("Cannot find struct '" + s.Name + "'", function.Type));
                            }
                        }
                    }
                } else if (typeEntity is Typedef) {
                    Typedef typedef = typeEntity as Typedef;
                    if (typedef.Parent == null) {
                        if (typedef.FileName == function.FileName) {
                            if (typedef.OrderIndex > function.OrderIndex) {
                                ErrorSink.AddError(ErrorSink.Error.Create("Cannot access typedef '" + typedef.CreatedType.GetQualifiedId() + "' because it has not yet been declared", function.Type));
                                return false;
                            }
                        } else {
                            if (!typedef.FileName.IsIncludedByFile(function.FileName)) {
                                ErrorSink.AddError(ErrorSink.Error.Create("Cannot find typedef '" + typedef.CreatedType.GetQualifiedId() + "'", function.Type));
                            }
                        }
                    }
                }

                if (!function.IsPrototype && function.Type.GetQualifiedId() != "void") {
                    if (!function.HasValidReturnStatement()) {
                        ErrorSink.AddError(ErrorSink.Error.Create("Function '" + function.Name + "' does not return a value", function));
                        return false;
                    }
                }

                bool hasFoundNonVariableChild = false;
                bool childrenValid = true;
                foreach (Entity child in function.Children) {
                    if (!(child is Variable)) {
                        hasFoundNonVariableChild = true;
                    } else {
                        if (hasFoundNonVariableChild) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Variables must be declared in the top of a function", child));
                            return false;
                        }
                    }

                    ITypeValidator typeValidator = ValidationEntryPoint.GetTypeValidatorForType(new [] { child.GetType() });
                    if (typeValidator != null) {
                        bool valid = typeValidator.Validate(child);
                        if (!valid) {
                            childrenValid = false;
                        }
                    }
                }

                return childrenValid;
            }

            return false;
        }
    }
}
